/*
 * Copyright 2011 PA Consulting Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.prodeagle.java;

import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

public class Authentication {
	private static final Logger _logger = Logger.getLogger(Authentication.class.getSimpleName());

	private static final String NAMESPACE = "prodeagle";
	private static final String SECURE_HOST = "https://prod-eagle.appspot.com";

	public static String getKeySecret(HttpServletRequest req, HttpServletResponse resp) {
		return getKeySecret(req, resp, null);
	}

	public static String getKeySecret(HttpServletRequest req, HttpServletResponse resp, String updateAuth) {
		String originalNamespace = NamespaceManager.get();
		NamespaceManager.set(NAMESPACE);

		try { //put in a try so that we can have a finally block which resets namespaces
			Key key = KeyFactory.createKey("AuthKey", "master");

			DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
			MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService();

			// look for the auth key in the datastore
			Entity authEntity = (Entity) memcacheService.get(KeyFactory.keyToString(key));
			if (authEntity == null) {
				try {
					authEntity = datastoreService.get(key);
					memcacheService.put(KeyFactory.keyToString(key), authEntity);
				} catch (EntityNotFoundException e) {
					_logger.info("Authentication entity not found in memcache or datastore");
					authEntity = null;
				}
			}

			if (null != updateAuth) {
				//if we're updating the auth
				if (null == authEntity || !authEntity.getProperty("secret").equals(updateAuth)) {
					//if there is no auth key, or the auth key doesn't match the one provided
					try {
						URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
						String host = URLEncoder.encode(req.getHeader("Host"), "UTF-8");
						HTTPResponse response = fetchService.fetch(new URL(SECURE_HOST + "/auth/?site=" + host + "&auth=" + updateAuth));

						if (response.getResponseCode() == 200) {
							//if successfully received a response, save the new auth key
							String content = new String(response.getContent());
							if (content.equals(new String("OK"))) {
								authEntity = new Entity(key);
								authEntity.setProperty("secret", updateAuth);

								datastoreService.put(authEntity);
								memcacheService.put(KeyFactory.keyToString(key), authEntity);
							}
						}
					} catch (Exception e) {
						_logger.severe("Exception while updating key: " + e);
						return null;
					}
				}
			}

			if (null != authEntity) {
				//if the key did match or we've since updated it, return it
				return (String) authEntity.getProperty("secret");
			} else {
				//otherwise return nothing
				return null;
			}
		} finally {
			//always reset the namespace
			NamespaceManager.set(originalNamespace);
		}
	}

	public static Boolean isProdEagle(HttpServletRequest req, HttpServletResponse resp) {
		String possibleAuth = req.getParameter("auth");

		if (null != possibleAuth) {
			String secret = getKeySecret(req, resp, possibleAuth);

			if (null != secret && secret.equals(possibleAuth)) {
				return true;
			}
		}

		return false;
	}

	public static Boolean isAdministrator(HttpServletRequest req, HttpServletResponse resp) {
		UserService userService = UserServiceFactory.getUserService();

		User user = userService.getCurrentUser();

		try {
			if (null == user) {
				String loginUrl = userService.createLoginURL(req.getRequestURI() + "?" + req.getQueryString());
				_logger.info("Sending redirect to: " + loginUrl);
				resp.sendRedirect(loginUrl);
				return false;
			} else if (userService.isUserAdmin()) {
				return true;
			} else {
				_logger.info("User is logged in, but not an admin");
				resp.getWriter().print(String.format(
						"Please login with an administrator account. <a href=\"%s\">Logout</a>",
						userService.createLogoutURL(req.getRequestURI() + "?" + req.getQueryString())));
			}
		} catch (IOException e) {
			_logger.severe("Failure to write logout info. Exception: " + e);
		}

		return false;
	}

	public static void addUser(HttpServletRequest req, HttpServletResponse resp) {
		if (isAdministrator(req, resp)) {
			_logger.info("User is an admin. Getting secret");
			String secret = getKeySecret(req, resp);

			try {
				if (null == secret) {
					resp.getWriter().print("ProdEagle hasn't set your secret yet. Please visit prodeagle.com and register your website.");
					return;
				} else {
					String type = "administrator";
					String email = req.getParameter("administrator");
					if (null != req.getParameter("viewer")) {
						type = "viewer";
						email = req.getParameter("viewer");
					}

					String host = URLEncoder.encode(req.getHeader("Host"), "UTF-8");
					resp.sendRedirect(SECURE_HOST + "/auth/?site=" + host + "&auth=" + secret + "&" + type + "=" + email);
				}
			} catch (IOException e) {
				_logger.severe("Failure to write response for adding a user. Exception: " + e);
			}
		} else {
			_logger.info("User is not an admin. Cannot add user");
		}
	}
}
